package com.capsulode.excel;

import com.capsulode.excel.annotation.Header;
import com.capsulode.excel.conversion.ConverterManager;
import com.capsulode.excel.conversion.Writer;
import com.capsulode.excel.conversion.impl.ObjectConverter;
import com.capsulode.excel.exception.CellTypeNotExpectException;
import com.capsulode.excel.exception.ConversionFailException;
import com.capsulode.excel.exception.UnsupportedConversionException;
import com.capsulode.excel.header.WritableHeader;
import com.capsulode.excel.util.ExcelUtil;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.ss.usermodel.*;

import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.Collection;
import java.util.function.Function;

public class ExcelTemplatedExporter<T> {
    private final Workbook workbook;
    private final Collection<WritableHeader<T>> headers = new ArrayList<>();
    private int startRowIndex = 0;

    public ExcelTemplatedExporter(Workbook template) {
        this.workbook = template;
    }

    public ExcelTemplatedExporter(InputStream templateInputStream, String contentType) throws IOException {
        this.workbook = ExcelUtil.guessInstanceFromContentTypeOrFilename(templateInputStream, contentType);
    }

    public ExcelTemplatedExporter<T> writeHttpHeader(String filename, HttpServletResponse response) {
        String mime = workbook instanceof HSSFWorkbook ? ExcelUtil.MIME_XLS : ExcelUtil.MIME_XLSX;
        String ext = workbook instanceof HSSFWorkbook ? ".xls" : "xlsx";
        String encodedFilename;
        try {
            encodedFilename = URLEncoder.encode(filename, "utf-8");
        } catch (UnsupportedEncodingException e) {
            throw new RuntimeException("UTF-8 encoding unsupported!?");
        }
        response.setContentType(mime + "; charset=utf-8");
        response.setHeader("Content-Disposition", "attachment; filename=" + encodedFilename + ext + "; filename*=UTF-8''" + encodedFilename + ext);
        return this;
    }

    /**
     * 定义导出的列.
     *
     * @param columnIndex 0-base index of column
     * @param serializer  a function to expose field
     * @param <F>         field type
     * @return this
     */
    public <F> ExcelTemplatedExporter<T> column(int columnIndex, Function<T, F> serializer) throws IOException {
        headers.add(new SimpleWritableHeader<>(columnIndex, serializer, ObjectConverter.DEFAULT_INSTANCE));
        return this;
    }

    public ExcelTemplatedExporter<T> schema(Class<T> type) {
        if (type == null) {
            throw new IllegalArgumentException("Excel row data wrapper bean type not specified.");
        }
        for (Field field : type.getDeclaredFields()) {
            Header headerDefine = field.getAnnotation(Header.class);
            if (headerDefine != null) {
                int cellIndex = headerDefine.columnIndex();
                if (cellIndex < 0) {
                    throw new IllegalStateException("Header's column index unset!");
                }
                Class headerType = field.getType();
                final String fieldName = field.getName();

                Writer writer;
                Method getter;
                try {
                    writer = ConverterManager.newConverter(headerType, headerDefine);
                    getter = type.getMethod("get" + Character.toUpperCase(fieldName.charAt(0)) + fieldName.substring(1));
                } catch (UnsupportedConversionException ex) {
                    throw new IllegalArgumentException("不能识别的字段:" + fieldName + ",类型：" + headerType, ex);
                } catch (ReflectiveOperationException ex) {
                    throw new IllegalArgumentException("字段值无法访问:" + fieldName + ",类型:" + headerType, ex);
                }
                headers.add(new SimpleWritableHeader<T, Object>(cellIndex, obj -> {
                    try {
                        return getter.invoke(obj);
                    } catch (ReflectiveOperationException ex) {
                        throw new RuntimeException("导出错误：反射方式读取JavaBean失败", ex);
                    }
                }, writer));
            }
        }
        return this;
    }

    public ExcelTemplatedExporter<T> startAt(int rowNumber) {
        this.startRowIndex = rowNumber - 1;
        return this;
    }

    public void export(Collection<T> datas, OutputStream out) throws IOException {
        Sheet sheet = workbook.getSheetAt(0);
        int rowNumber = startRowIndex;
        injectColumnStyles(sheet.getRow(rowNumber));

        for (T data : datas) {
            Row row = sheet.createRow(rowNumber++);
            for (WritableHeader<T> header : headers) {
                Cell cell = row.createCell(header.getIndex());
                cell.setCellStyle(header.getStyle());
                try {
                    header.write(cell, data);
                } catch (ConversionFailException | CellTypeNotExpectException ex) {
                    throw new RuntimeException("导出出错", ex);
                }
            }
        }
        workbook.write(out);
    }

    public void export(Collection<T> datas, HttpServletResponse response) throws IOException {
        try (ServletOutputStream writer = response.getOutputStream()) {
            export(datas, writer);
        }
    }

    private void injectColumnStyles(Row row) {
        CellStyle defaultCellStyle = workbook.getCellStyleAt(0);
        for (WritableHeader<T> header : headers) {
            if (row == null) {
                header.setStyle(defaultCellStyle);
                continue;
            }
            Cell cell = row.getCell(header.getIndex());
            if (cell == null) {
                header.setStyle(workbook.getCellStyleAt(0));
                continue;
            }
            CellStyle cellStyle = cell.getCellStyle();
            header.setStyle(cellStyle);
        }
    }

    private class SimpleWritableHeader<R, F> implements WritableHeader<R> {
        private final int index;
        private CellStyle style;
        private final Function<R, F> fieldReader;
        private final Writer writer;


        public SimpleWritableHeader(int index, Function<R, F> fieldReader, Writer writer) {
            this.index = index;
            this.fieldReader = fieldReader;
            this.writer = writer;
        }

        @Override
        public int getIndex() {
            return index;
        }

        @Override
        public CellStyle getStyle() {
            return style;
        }

        public void setStyle(CellStyle style) {
            this.style = style;
        }

        @Override
        public void write(Cell cell, R obj) throws ConversionFailException, CellTypeNotExpectException {
            F value = fieldReader.apply(obj);
            if (value != null) {
                writer.serialize(value, cell);
            }
        }
    }
}
